Skip to content

fix(platform-objects): 21 zh-CN metadata-form leaves say what their source says - #16226

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-14931-zhcn-metadata-form-leaves
Sep 6, 2026
Merged

fix(platform-objects): 21 zh-CN metadata-form leaves say what their source says#16226
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-14931-zhcn-metadata-form-leaves

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #14931

Twenty-one zh-CN metadataForms leaves in
packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts assert
something their English source does not, or drop a distinct concept it names. One line
each; the file's structure, its key set and its 158 byte-identical-to-en fills are
untouched.

Authored by Claude Code in session session_01ARYe3yQTQCUFm5qPYNgKaJ, dispatched on
issue #14931; the claim record is comment 5557212975 on that card.

Subtraction first — 21 of the enumerated 26, measured before any edit

The census on #14587 (comment 5524387962) enumerated 26 leaves. Five have already
landed, all in the dashboard subtree, all in one commit 9f57f1e31 (#14926). Both
bundles were flattened to dotted leaf paths at origin/main 9f3989771: 773 leaves
each, key sets identical, 158 byte-identical to en, 615 differing
(the census read
767 / 154 / 613 — the bundle has gained six leaves since). Every one of the 26 was then
compared, by text, against that tree.

landed leaf value at the census value on 9f3989771
dashboard.sections.basics.description 名称与图标 仪表板标识与描述
dashboard.sections.layout.description 栅格与响应式 栅格尺寸与刷新间隔
dashboard.sections.widgets.description 图表、指标、列表等 放置在栅格上的卡片与图表
dashboard.sections.filters.description 全局筛选与日期范围 应用到所有组件的默认筛选与全局筛选
dashboard.fields.header.helpText 标题、操作按钮与筛选 仪表板页眉配置(title、subtitle、actions)

⚠️ dashboard.fields.gap.helpText, repaired by #16163, is not one of the 26 and does
not move the count. The remaining 21 are this PR, and every one still carried its
census value at 9f3989771.

⛔ The 18 looser paraphrases the census deliberately excluded are untouched, and so are
es-ES and ja-JP.

Grouped by defect kind, with the source quoted

A · Asserts an input or a concept that does not exist

leaf source (en) before after
skill.sections.triggers.description When this skill should activate. 触发关键词与上下文 此技能应在何时激活
email_template.fields.variables.helpText [{ "name": "user.name", "type": "string", "required": true, "description": "..." }] 模板内可引用的变量与默认值 示例:[{ "name": "user.name", … }]
action.sections.advanced.description AI exposure and API request shape. 批量与 AI 暴露 AI 暴露与 API 请求体结构
page.sections.basics.description Page identity and template. 名称、标题与图标 页面标识与模板
email_template.sections.identity.description Template identifier resolved by IEmailService.sendTemplate(...). 模板 ID 与正文类型 模板标识符,由 IEmailService.sendTemplate(...) 解析。
app.fields.defaultAgent.helpText Platform agent for the ambient assistant ('ask' by default; 'build' for authoring surfaces) 右下角浮动助手按钮调用的 AI 智能体 环境助手使用的平台智能体(默认 'ask';创作类界面用 'build')
  • skill.sections.triggers holds exactly one field, triggerConditions
    (packages/spec/src/ai/skill.form.ts:35-42). triggerPhrases was removed with the key —
    the form's own comment records that phrases were never matched. 「触发关键词」 promised a
    keyword input that is not there.
  • email_template.fields.variablesEmailTemplateDefinitionVariableSchema
    (packages/spec/src/system/email-template.zod.ts:37-42) declares name / type /
    required / description. There is no default value. The leaf promised
    「默认值」. The replacement follows the sibling email_template.fields.fromOverride.helpText,
    which is already 「示例:{ "name": "Acme Sales", … }」 — the bundle's own way of
    translating a helpText that IS a JSON literal.
  • action.sections.advancedbulkEnabled was removed from this section
    (packages/spec/src/ui/action.form.ts:81-82); it now holds ai, recordIdParam,
    recordIdField, bodyShape. 「批量」 named a control that is gone, while 「API 请求体结构」 —
    the exact phrase the bundle already uses at action.fields.bodyShape.label — was missing.
  • page.sections.basics holds name, label, icon, type, template,
    description; 「名称、标题与图标」 enumerated three of six and dropped template, the one
    concept the source raises to section level. 「页面标识与模板」 mirrors the landed
    「仪表板标识与描述」.
  • app.fields.defaultAgent — the source names no button and no screen position;
    「右下角浮动助手按钮」 invented both, and the 'ask' / 'build' defaults were missing.
    「环境助手」 is this bundle's own rendering of "ambient assistant"
    (app.sections.content.description), and 「智能体」 its own for "agent"
    (app.fields.defaultAgent.label).

B · Names the wrong technology

leaf source (en) before after
action.fields.body.helpText Either an L1 expression or an L2 sandboxed JS body 要执行的 JavaScript 代码 L1 表达式或 L2 沙箱 JS 体
page.fields.type.helpText Page kind. "List / Interface" binds a source view into a curated surface — … 页面种类 — list / record / home / app / utility。… 页面种类。"List / Interface" 把来源视图绑定为经过编排的界面——…
  • action.fields.body told a zh author the body is JavaScript. An L1 expression is not
    JavaScript, and the L2 body is sandboxed. The replacement is verbatim the sibling
    hook.fields.body.helpText, which translates the identical source sentence correctly —
    ⛔ not a third phrasing invented here.
  • page.fields.type carried a superseded enumeration (list / record / home / app / utility) that the source no longer names, and dropped the claim it does make — that
    "List / Interface" binds a source view into a curated surface. The option labels are not
    in this bundle, so the author sees List / Interface page in English; the quoted token is
    therefore kept in English too. The trailing sentence was already correct and is carried
    through unchanged.

C · Drops a distinct concept the source names

leaf source (en) before after
object.fields.isSystem.helpText System object (protected from deletion; defaults sharing to public) 系统对象(受保护,不可删除) 系统对象(受保护,不可删除;共享默认为公开)
object.fields.fields.trackHistory.helpText Summarize this field on the record activity timeline 保留变更历史 在记录的活动时间线上概述该字段的变更
view.fields.filter.helpText Filter conditions — same visual builder as the list toolbar, with field-type-aware operators and value inputs 筛选规则 筛选条件——与列表工具栏相同的可视化构建器,运算符与取值输入随字段类型变化
page.sections.advanced.description Activation, audience, and accessibility. 默认页、类型与分配 启用状态、适用人群与无障碍
report.sections.basics.description Identity and report type. 名称与数据源 标识与报表类型
report.sections.joined_blocks.description Additional dataset-bound blocks stacked into a single report (joined reports only). 跨对象联合查询 叠加进同一张报表的其他数据集分块(仅 joined 报表)
report.fields.columns.helpText Dimension names across (matrix only) 报表中显示的列 横向展开的维度名(仅 matrix 报表)
email_template.sections.subject.description Subject line. Supports {{var.path}} interpolation. 邮件主题模板 主题行。支持 {{var.path}} 插值。
permission.sections.identity.description Permission Sets stack on top of a Profile to grant additional access. Profiles are the base set assigned 1:1 to each user. 权限集或配置文件标识 权限集叠加在配置文件之上,用于授予额外访问权限。配置文件是按 1:1 分配给每个用户的基础权限集。
permission.sections.system_permissions.description High-level capabilities not tied to a specific object — e.g. manage_users, view_audit_logs. 应用、API、管理与设置访问 不针对特定对象的高层能力——如 manage_users、view_audit_logs
permission.sections.object_and_field_permissions.description Per-object CRUD + per-field FLS. Edit via the matrix editor or paste JSON here. 增删改查与字段可见性 对象级增删改查 + 字段级安全(FLS)。可用矩阵编辑器编辑,或在此粘贴 JSON。
permission.fields.systemPermissions.helpText List of system capability keys 应用访问、API、管理操作 系统能力键列表
skill.fields.description.helpText What this skill does AI 用来判断是否调用的简短说明 此技能的作用

Two of these were saying something the code contradicts rather than merely saying less:
skill.fields.description claimed the field is what the AI reads to decide whether to
invoke the skill — the form's own comment records that routing is triggerConditions plus
the agent allowlist — and permission.fields.systemPermissions replaced "capability keys"
with a three-way categorisation the source never draws.

Word choices, argued against this bundle's own usage

Every replacement re-uses a term this bundle already uses for the same concept, rather than
a fresh one. Counts are over the 773 zh-CN leaves of this file.

source concept chosen zh why, from this bundle
identity 标识 (22) object.sections.basics 「标识、显示名与分类标签」; the landed 「仪表板标识与描述」
activation 启用状态 app.sections.basics.description translates the same English word "activation" as 「启用状态」
accessibility 无障碍 (3) page.fields.aria.labeldashboard.sections.advanced.description
ambient assistant 环境助手 (1) app.sections.content.description
request shape 请求体结构 action.fields.bodyShape.label
filter conditions 筛选条件 dashboard.fields.globalFilters.helpText
per-object / per-field 按对象 / 按字段 permission.fields.objects.helpTextpermission.fields.fields.helpText — the two fields this very section holds
RLS / FLS 行级安全 / 字段级安全 permission.sections.tab_and_row_level_security.label is already 「标签页与行级安全」; 字段级安全 is its exact parallel
blocks / dimension / measure 分块 / 维度 / 度量 report.fields.blocks.labelreport.fields.order.helpText
joined / matrix report kinds kept in English report.fields.type.helpText 「报表类型:tabular / summary / matrix / joined」
activity timeline 活动时间线 the repo's own zh term (examples/app-showcase/src/system/translations/index.ts, packages/lint/src/showcase-shape.fixtures.ts)

Three calls needed a decision rather than a lookup, and are recorded as such:

  • 「概述」 for "Summarize", not 「汇总」 or 「摘要」. 汇总 is already taken in this same
    bundle by roll-up aggregation (field.fields.summaryOperations.label 「汇总操作」), and
    摘要 is used elsewhere in the repo for messaging digests
    (service-messaging/src/translations/zh-CN.objects.generated.ts). Both would be false
    friends here.
  • 「运算符」 for "operators"zero in-repo precedent either way (运算符 and
    操作符 both return 0 hits across packages/, examples/); the standard term was taken.
  • 「插值」 for "interpolation" — likewise zero in-repo precedent. The literal
    {{var.path}} token is kept verbatim, which is what actually tells the author what to type.

Punctuation register, measured, not assumed: 60 of the 66 translated section
descriptions in this file do not end in , and 205 of 212 translated helpTexts do not
either; the six that do are multi-sentence. Single-clause replacements here therefore carry
no trailing period, and the five multi-sentence ones do.

⛔ Every instrument here is blind to this class — the gates are reported, not claimed

check-i18n-stale-fill detects a leaf that is a byte copy of a previous source
revision, via cross-locale agreement or a recorded digest. These 615 legacy-trusted leaves
are neither, and the gate's own header states the general case: once a leaf differs from
current source, a stale fill is indistinguishable by value from a real translation. The
card proved this by ablation. ⇒ No green below is evidence that this change was needed or
that it worked.
The acceptance evidence is the leaf-by-leaf table above.

Regeneration does not correct these either: bundle merge fills gaps only, and a
present-but-wrong leaf is not a gap.

Gate family — all 49 derived families, run at 7b890a69e

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
(no hand-written path list): 40 named by path + 2 by change kind + 7 declared whole-tree.
All 49 exit 0. Exit codes were captured with redirect-then-read, never through a pipe.

Two returned exit 3 = PREREQUISITE NOT MET on the first pass — check:i18n (workspace
CLI not built) and check:dual-build-cjs-loads (packages without dist/). Those are
NOT MEASURED, not reds. Both prerequisites were then cleared with a real build and both
re-run green:

  • check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).
  • check:dual-build-cjs-loads — 103 published require entry point(s) across 66 package(s) load; 619 emitted CommonJS file(s) parse
  • check-i18n-stale-fill: OK (10 bundle set(s) — no new stale fills, 0 baselined). · 27 provenance companions, 27 served, 0 unserved
  • check-dts-closure: 57 built package(s) swept - 147/147 declared declaration file(s) present — and packages/platform-objects/dist is in that swept set (verified: the 8-package first pass was exactly the 8 directories carrying a dist/, platform-objects among them)

Package-level: @objectstack/platform-objects35 test files, 528 tests, all passing;
typecheck clean, and tsc --listFiles confirms the edited file is in that program
(1 hit), so the green is about this file rather than around it.

Repo-wide pnpm lint was narrowed, and the narrowing is measured rather than assumed:
(1) eslint --print-config on the edited file returns 6 configured rules, so it is in
eslint's own swept population; (2) --format json over the diff reports 2 files handed in,
1 actually linted (the changeset .md is "ignored because no matching configuration"),
0 errors, 0 warnings; (3) languageOptions.parserOptions.project and projectService
are both nulltype-aware linting is not enabled, so editing string values in one
file cannot move the verdict on any untouched file; (4) instrument liveness — a control
file placed in the same directory under the same config produced 1 no-restricted-imports
error
, so the zero above is a reading and not a silence.

Clause-② — no, and non-vacuously so

Measured by ablation at 7b890a69e, not asserted. @objectstack/platform-objects publishes
files: ["dist", …], so the published surface is all 66 files under dist/ — the whole
set was compared, ⛔ not the root barrel.

HEAD blob         69c272152964cb2314f0dff06562d4209daffdc7
mutation          the edited file swapped back to origin/main 9f3989771
                  markers: repaired-lines 0 (expect 0) · original-lines 2 (expect 2)
                  on-disk blob d30a8d7c… != HEAD blob  ⇒ the mutation is really on disk
rebuild proven    dist/index.js mtime 1788673935 -> 1788674362 (and index.d.ts 1788673952 -> 1788674379)
restore           blob 69c27215… == HEAD blob AND `git diff HEAD` empty; `git status --porcelain` empty
head rebuild      reproduces the pre-ablation dist BYTE-FOR-BYTE, 66/66 files

Result across the ablation:

  • 0 of 22 published declaration files differ (dist/**/*.d.ts and *.d.mts, every
    entry point plus every subpath — apps, audit, identity, integration,
    metadata, metadata-translations, pages, plugin, security, system, root).
  • 12 published files DID move in the same proven-rebuilt run: index.js, index.mjs,
    metadata-translations/index.js, metadata-translations/index.mjs, plugin.js,
    plugin.mjs and their six sourcemaps.
  • Hunk classification on dist/metadata-translations/index.js: exactly 42 changed lines
    = the 21 pairs
    , every one a string-literal value. No declaration, no export, no
    signature. (esbuild emits the CJK as \uXXXX; two hunks switch the surrounding quote
    from " to ' because the value itself contains " — still a value.)

⇒ The declaration surface is provably still, while the runtime content provably moved. Per
the standing #16076 ruling, a string-literal VALUE inside an emitted data type is not limb-1
movement.

Changeset — patch, re-derived rather than taken

npm pack --dry-run --json in packages/platform-objects: not private, 69 entries, 66
dist/ files in the tarball and 0 src/ files
; dist/index.js,
dist/metadata-translations/index.js, dist/plugin.js and dist/index.d.ts are all in it.
Scanning the 22 published JS files for each new value in its emitted \uXXXX form:
21 / 21 present, 6 files each, and the old action.fields.body value is present in 0.
⇒ Published content changes for a zh-reading Studio author ⇒ patch, ⛔ not skip-changeset.
No API surface moves ⇒ ⛔ not minor.

objectui overlay — checked per leaf, none of the 21 is shadowed

packages/app-shell/src/views/metadata-admin/metadata-form-i18n.ts at objectui
origin/main 3197379 resolves bundleLocale() to 'zh-CN' for zh, so a zh leaf CAN be
shadowed there. Its METADATA_FORM_BUNDLE declares exactly one type key — dashboard
(measured: 1 hit at the type indent, with a firing control returning 2 at the next indent
down). Every leaf this PR touches is under object, view, page, app, action,
report, email_template, permission or skill, so none of the 21 is shadowed, and
⛔ nothing in objectui is edited here. That is also why the five already-landed leaves were
the ones that needed overlay matching and these do not.

Reported, not repaired here

⚠️ The card's reading that "es-ES and ja-JP are correct for this class; zh-CN is alone"
was established at two leaves and does not generalise. Measured across the 21: es-ES and
ja-JP carry the same defect at nine of them, including the two the card singles out —
action.fields.body.helpText reads Código JavaScript que ejecutar / 「実行する JavaScript
コード」 in exactly the way zh-CN did. Filed separately with the table rather than widened
into here; ⛔ those files are also PR #16190's right now.

Related: #14587 (the census, comment 5524387962) · #14926 · #16163 · #16190.


🤖 Generated with Claude Code

https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ


Generated by Claude Code

…ource says

The 613 LEGACY-TRUSTED zh-CN leaves in `zh-CN.metadata-forms.generated.ts` — differing
from `en`, carrying no digest — were imported from a pre-consolidation hand vocabulary in
`e0077ea36` and never reconciled against the seeded English. The census on #14587 read all
613 and found 26 that assert something the source does not, or drop a distinct concept the
source names. Five (the whole `dashboard` subtree) landed in `9f57f1e31`; these are the
remaining 21, verified still wrong against `origin/main` `9f3989771` before any edit.

No instrument here can see them, by construction: `check-i18n-stale-fill` detects a byte
copy of a previous source revision, and these are hand-written mistranslations. Merge does
not correct them either — it fills gaps only.

Each replacement is argued against this bundle's own usage rather than freshly invented:
`action.fields.body.helpText` takes the sibling `hook.fields.body.helpText` verbatim for
the identical source sentence; 「环境助手」 is the bundle's own rendering of "ambient
assistant"; 「请求体结构」 is its own for "request shape"; 「按对象 / 按字段」 comes from
the two fields the permission section holds; 「分块」、「维度」、「度量」 come from the
report subtree; and the no-trailing-period register follows 60 of 66 translated section
descriptions.

zh-CN only. es-ES and ja-JP are not touched, nor are the 18 looser paraphrases the census
deliberately excluded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/platform-objects, touching 1 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/email-templates.mdx (via email_template (symbol, a field of const object zhCNMetadataForms))
  • content/docs/concepts/metadata-lifecycle.mdx (via email_template (symbol, a field of const object zhCNMetadataForms))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via email_template (symbol, a field of const object zhCNMetadataForms))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 8 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json cbca47d09bc00b237f1f75046f03205dd4da2d32packageMentionDocs.

Which tree this was computed on

This run read content/docs from 138eb98e0bb4839ca31019de3009a0385bf098d1 — the merge of head 7b890a69eba38e24172cfdcff16142e1b8bb05ec into base cbca47d09bc00b237f1f75046f03205dd4da2d32, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 138eb98e0bb4839ca31019de3009a0385bf098d1 && git checkout 138eb98e0bb4839ca31019de3009a0385bf098d1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cbca47d09bc00b237f1f75046f03205dd4da2d32 7b890a69eba38e24172cfdcff16142e1b8bb05ec && git checkout -B drift-repro cbca47d09bc00b237f1f75046f03205dd4da2d32 && git merge --no-ff 7b890a69eba38e24172cfdcff16142e1b8bb05ec

node scripts/docs-audit/affected-docs.mjs --json cbca47d09bc00b237f1f75046f03205dd4da2d32

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs cbca47d09bc00b237f1f75046f03205dd4da2d32 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

2 participants